package com.apple;

import cn.xiaohuodui.utils.json.JsonUtil;
import com.apple.http.HttpConnection;
import com.apple.http.MediaType;
import org.apache.commons.codec.binary.Base64;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * A utility class for validating Apple in app purchase reciepts
 *
 * @author Michael Da Silva (kronoshadow)
 */
public class AppleReceiptValidation {
    //verify reciept URLs
    public static final String TEST_VALIDATE_RECEIPT_URL = "https://sandbox.itunes.apple.com/verifyReceipt";
    public static final String PRODUCTION_VALIDATE_RECIEPT_URL = "https://buy.itunes.apple.com/verifyReceipt";

    private AppleReceiptValidation() {
    }

    /**
     * Validates an Apple app store receipt to verify a purchase was made.
     *
     * @param receipt a <code>String</code> containing the receipt data of the in app purchase
     * @param base64  a <code>boolean</code> to indicate whether the receipt need to base64 encoode.
     * @param test    a <code>boolean</code> to indicate whether the the validation process should use Apple's sandbox for testing
     * @return a Jackson Gson <code>JsonNode</code> containing the JSON returned from the API
     * @throws AppleReceiptValidationFailedException gets thrown when the response from the Apple API server responded in an unexpected way or the receipt is invalid
     * @see <a href="https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1">Apple Developer - Validating Receipts With The App Store</a>
     */
    public static JsonNode validateReciept(String receipt, boolean base64, boolean test) throws AppleReceiptValidationFailedException {
        if (base64) {
            //encode receipt data using base64
            receipt = new String(Base64.encodeBase64(receipt.getBytes()));
        }

        //prepare a JSON with the receipt data for a request to Apple
        Map<String, String> receiptData = new HashMap<String, String>();
        receiptData.put("receipt-data", receipt);
        String json = JsonUtil.seriazileAsString(receiptData);

        //get the URL to use
        String validationUrl = test ? TEST_VALIDATE_RECEIPT_URL : PRODUCTION_VALIDATE_RECIEPT_URL;

        //attempt a connection
        String responseContent = "";
        try {
            responseContent = HttpConnection.sendSecureRequest(validationUrl, HttpConnection.RequestMethod.POST, MediaType.Application.JSON, json);
        } catch (HttpConnection.ConnectionFailedException cfEx) {
            throw new AppleReceiptValidationFailedException(20000, "Couldn't validate the receipt because the connection with the Apple API server had failed", cfEx);
        }

        //check response from Apple API server is not empty
        if (responseContent.isEmpty()) {
            throw new AppleReceiptValidationFailedException(20000, "Couldn't validate the receipt because the response from the Apple API server was empty.");
        }

        JsonNode responseJson = null;
        //parse the content from the Apple API server as a JSON object
        try {
            ObjectMapper mapper = new ObjectMapper();
            responseJson = mapper.readTree(responseContent);
        } catch (JsonParseException jpEx) {
            throw new AppleReceiptValidationFailedException("Couldn't validate the receipt because the response from the Apple API was a badly formed JSON", jpEx);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new AppleReceiptValidationFailedException("Couldn't validate the receipt because the response from the Apple API was a badly formed JSON", e);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //if the status returned is 0 then the validation was successful, all other statuses should throw an exception
        System.out.println("responseJson:" + responseJson.toString());
        if (responseJson != null) {
            int status = responseJson.get("status").getIntValue();
            if (status == 0) {
                JsonNode recieptJson = responseJson.get("receipt");
                return recieptJson;
            } else if (status == 21003) {
                throw new AppleReceiptValidationFailedException(21003, "Couldn't validate the receipt because the Apple API server returned "
                        + "[Status 21003: The receipt could not be authenticated.]");
            } else if (status == 21000) {
                throw new AppleReceiptValidationFailedException(21000, "Couldn't validate the receipt because the Apple API server returned "
                        + "[Status 21000: The App Store could not read the JSON object you provided]");
            } else if (status == 21002) {
                throw new AppleReceiptValidationFailedException(21002, "Couldn't validate the receipt because the Apple API server returned "
                        + "[Status 21002: The data in the receipt-data property was malformed or missing]");
            } else if (status == 21005) {
                throw new AppleReceiptValidationFailedException(21005, "Couldn't validate the receipt because the Apple API server returned "
                        + "[Status 21005: The receipt server is not currently available]");
            } else if (status == 21007) {
                if (!test) {
                    return validateReciept(receipt, true);
                } else {
                    throw new AppleReceiptValidationFailedException(21007, "Couldn't validate the receipt because the Apple API server returned "
                            + "[Status 21007: This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead]");
                }
            } else if (status == 21008) {
                throw new AppleReceiptValidationFailedException(21008, "Couldn't validate the receipt because the Apple API server returned "
                        + "[Status 21008: This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead]");
            }
        }
        throw new AppleReceiptValidationFailedException(20000, "Couldn't validate the reciept becuase the response from the Apple API server was empty.");
    }

    /**
     * Validates an Apple app store receipt to verify a purchase was made.
     *
     * @param receipt a <code>String</code> containing the receipt data of the in app purchase
     * @param test    a <code>boolean</code> to indicate whether the the validation process should use Apple's sandbox for testing
     * @return a Jackson Gson <code>JsonNode</code> containing the JSON returned from the API
     * @throws AppleReceiptValidationFailedException gets thrown when the response from the Apple API server responded in an unexpected way or the receipt is invalid
     * @see <a href="https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1">Apple Developer - Validating Receipts With The App Store</a>
     */
    public static JsonNode validateReciept(String receipt, boolean test) throws AppleReceiptValidationFailedException {
        return validateReciept(receipt, false, test);
    }

    public static class AppleReceiptValidationFailedException extends Exception {
        private int status;

        public AppleReceiptValidationFailedException() {
            super();
        }

        public AppleReceiptValidationFailedException(int status, String message) {
            super(message);
            this.status = status;
        }

        public AppleReceiptValidationFailedException(String message, Throwable cause) {
            super(message, cause);
        }

        public AppleReceiptValidationFailedException(int status, String message, Throwable cause) {
            super(message, cause);
            this.status = status;
        }

        public AppleReceiptValidationFailedException(Throwable cause) {
            super(cause);
        }


        public int getStatus() {
            return status;
        }

        public void setStatus(int status) {
            this.status = status;
        }
    }

}